﻿using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using NodeBasedSkillEditor.View;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using XDFLib.Extensions.Reflections;

namespace NodeBasedSkillEditor.ViewModel
{
    public class VM_PortCluster : ObservableObject
    {
        public RelayCommand OnDeleteCmd { get; set; }

        public PortCluster BindData { get; private set; }
        public PortClusterControl BindCtrl { get; private set; }
        public VM_PortClusterGroup ParentGroup { get; private set; }
        public int IndexInParentGroup => ParentGroup != null ? ParentGroup.Clusters.IndexOf(this) : -1;
        public UIElementCollection ClusterInputStack => BindCtrl.InputStack.Children;
        public UIElementCollection ClusterOutputStack => BindCtrl.OutputStack.Children;

        public List<VM_Port> AllPorts { get; private set; } = new List<VM_Port>();

        public VM_PortCluster(PortCluster clusterData, PortClusterControl bindCtrl,
            VM_PortClusterGroup parentGroup)
        {
            OnDeleteCmd = new RelayCommand(OnDelete);

            BindData = clusterData;
            BindCtrl = bindCtrl;
            BindCtrl.DataContext = this;
            ParentGroup = parentGroup;

            var typeRef = __makeref(clusterData);
            var fields = BindData.GetType().GetFields();
            foreach (var f in fields)
            {
                if (f.FieldType.IsAssignableTo(typeof(BasePort)))
                {
                    if (f.FieldType != typeof(PortGroup))
                    {
                        var port = f.GetValueDirect(typeRef) as BasePort;
                        AddPortFromField(f, port);
                    }
                    else
                    {
                        var portGroup = f.GetValueDirect(typeRef) as PortGroup;
                        AddPortGroupFromField(f, portGroup);
                    }
                }
            }
        }

        void AddPortFromField(FieldInfo portField, BasePort portData)
        {
            var portAttr = portField.GetCustomAttribute<PortAttribute>();
            if (portAttr != null && portAttr.IOType != EPortIOType.Unknown)
            {
                portData.PortLocalId = ParentGroup.ParentNode.GetNextValidPortLocalId(portData.PortLocalId);
                var portName = portField.Name;

                var portVM = new VM_Port(portData.PortLocalId, ParentGroup.ParentNode, portName,
                    portAttr.IOType, portData, portAttr.TypeGroup,
                    portAttr.ContextMode, portAttr.IsListPort);
                portVM.SetParentCluster(this);

                PortControl newPortCtrl = (portAttr.IOType == EPortIOType.Input) ?
                    new PortControl_Input() :
                    new PortControl_Output();
                portVM.SetupBindingPortCtrl(newPortCtrl);
                AddPortToDisplay(portVM, newPortCtrl);
            }
        }

        void AddPortGroupFromField(FieldInfo portGroupField, PortGroup portGroup)
        {
            var attr = portGroupField.GetCustomAttribute<PortAttribute>();
            if (attr != null && attr.IOType != EPortIOType.Unknown)
            {
                portGroup.PortLocalId = ParentGroup.ParentNode.GetNextValidPortLocalId(portGroup.PortLocalId);
                portGroup.ExpandPortData.PortLocalId = portGroup.PortLocalId;
                var portName = portGroupField.Name;

                var expandPortVM = new VM_Port(portGroup.PortLocalId, ParentGroup.ParentNode, portName,
                    attr.IOType, portGroup.ExpandPortData, attr.TypeGroup, attr.ContextMode, attr.IsListPort);
                expandPortVM.SetParentCluster(this);

                var portGroupCtrl = new PortGroupControl();
                portGroupCtrl.SizeChanged += ParentGroup.ParentNode.OnPortGroupSizeChanged;
                var portGroupVM = new VM_PortGroup(expandPortVM, portGroup, portGroupCtrl, attr.TypeGroup);
                //portGroupVM.SetParentCluster(this);
                AddPortToDisplay(expandPortVM, portGroupCtrl);
            }
        }

        void AddPortToDisplay(VM_Port portVM, Control ctrl)
        {
            AllPorts.Add(portVM);
            ParentGroup.ParentNode.AddPort(portVM, true);
            if (portVM.IOType == EPortIOType.Input)
            {
                ClusterInputStack.Add(ctrl);
            }
            else if (portVM.IOType == EPortIOType.Output)
            {
                ClusterOutputStack.Add(ctrl);
            }
        }

        public void OnSubPortOfGroupChanged(int change, VM_Port portVM)
        {
            if (change > 0) AllPorts.Add(portVM);
            else if (change < 0) AllPorts.Remove(portVM);
        }

        public void RemoveAllPortVmFromParentNodeAfterDelete()
        {
            foreach (var p in AllPorts)
            {
                ParentGroup.ParentNode.DeletePortOnDeltetingCluster(p);
            }
        }

        private void OnDelete()
        {
            ParentGroup.ParentNode.ParentDAG.DeletePortCluster(this);
            //RemoveAllPortVmFromParentNode();
        }
    }
}
